package cha.app.pong;

import java.util.List;
import java.util.Random;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnKeyListener;

public class Pong extends View implements SensorEventListener, OnKeyListener{
  private final static String TAG = "Pong";
  private static final int ZERO_AREA = 0;
  
  private ShapeDrawable paddle, ball;     //shapes for paddle and ball
  private int pad_length, ball_length;    //length of each shape
  private SensorManager sm;               //the sensor manager
  private Sensor s;                       //used to represent the orientation sensor
  private int pad_x, pad_y, pad_vel;      //position and velocity of the paddle
  private int scr_height, scr_width;      //resolution of the screen
  private boolean isRunning = true;       //controls ball control thread
  private Runnable runner;                //thread that controls ball movement
  
  public Pong( Context context, int difficulty, DisplayMetrics screen ) {
    super( context );
    scr_height = screen.heightPixels;
    scr_width = screen.widthPixels;
    
    //initial position of shapes
    pad_x = scr_width/2; pad_y = scr_height - 50; pad_vel = 0;  
    
    //set up the sensor stuff
    sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
    List <Sensor>orientSensor = sm.getSensorList( Sensor.TYPE_ORIENTATION );
    
    //initialize 2D shapes
    ball = new ShapeDrawable(new RectShape());
    paddle = new ShapeDrawable(new RectShape());
    
    paddle.getPaint().setColor(Color.WHITE);
    
    //size of the paddle and ball are determined by difficulty
    switch(difficulty){
      case Utilities.EASY_MODE:
        pad_length = 60;
        ball_length = 14;
        break;
      case Utilities.NORM_MODE:
        pad_length = 40;
        ball_length = 10;
        break;
      case Utilities.HARD_MODE:
        pad_length = 20;
        ball_length = 6;
        break;
    }
    paddle.setBounds( pad_x , pad_y - 5, pad_x + pad_length, pad_y);
    
    //check if there's at least 1 orientation sensor on the phone. If there is
    //use the first one.
    s = null;
    if(orientSensor.size() > 0){
      Log.d( TAG, "Orientation Sensor Found." );
      s = orientSensor.get(0);
      sm.registerListener( this, s, SensorManager.SENSOR_DELAY_GAME);
    }
    //if there is no orientation sensor, arrow keys will control the paddle.
    else{
      Log.d( TAG, "Orientation Sensor Missing." );
      setFocusable(true);
      setOnKeyListener(this);
    }
    
    //start the ball control thread
    runner = new BallDaemon(context);
    new Thread(runner).start();
  }
  
  /*
   * this is called when the game ends. the sensor is unregistered, and 
   * isRunning is set to false.
   */
  public void destroy(){
    if(s != null){
      sm.unregisterListener(this, s);
    }
    isRunning = false;
  }
  
  /*
   * (non-Javadoc)
   * @see android.view.View#onDraw(android.graphics.Canvas)
   * redraws all the 2D elements
   */
  protected void onDraw(Canvas canvas) {
    ball.draw(canvas);
    paddle.draw(canvas);
  }
  
  @Override
  public void onAccuracyChanged( Sensor sensor, int accuracy ) {
    ;
  }

  @Override
  /*
   * React every time a sensors value changes. In this case, only a change
   * caused by the orientation sensor will be considered.
   */
  public void onSensorChanged( SensorEvent event ) {
    if(event.sensor.getType() == Sensor.TYPE_ORIENTATION){
      //orientation values (degrees)
      float roll = event.values[2]; 
      
      if(Math.abs(roll) > ZERO_AREA){
        pad_vel = (int)( (roll - ZERO_AREA)/2 );  //this is used when the paddle hits the ball
        pad_x -= ((roll - ZERO_AREA)/2);
      
        //bound checking on the paddle
        pad_x = (pad_x < 0) ? 0 : pad_x;
        pad_x = (pad_x + pad_length > scr_width) ? scr_width - pad_length : pad_x;
        
        paddle.setBounds( pad_x, pad_y - 5, pad_x +  pad_length, pad_y);
      }
    }
    invalidate(); //redraw all 2D shape elements
  }
  
  @Override
  public boolean onKey( View v, int keycode, KeyEvent key_event ) {
    boolean ret_val = false;
    pad_vel = 0;
    switch(keycode){
      case KeyEvent.KEYCODE_DPAD_LEFT:
        pad_vel = -3;
        pad_x-=3;
        ret_val = true;
        break;
      case KeyEvent.KEYCODE_DPAD_RIGHT:
        pad_vel = 3;
        pad_x+=3;
        ret_val = true;
        break;
    }
    paddle.setBounds( pad_x, pad_y - 5, pad_x +  pad_length, pad_y);
    invalidate();  
    return ret_val;
  }
  
  
  /*
   * This inner class provides the thread that handles the ball's movement
   * animation. As well as handling collision detection for the ball.
   */
  public class BallDaemon extends View implements Runnable{
    public BallDaemon( Context context ) {
      super( context );
    }

    private int x_vel, y_vel;   //ball velocity values
    private int ball_x, ball_y; //ball position values
    @Override
    public void run() {
      Log.d(TAG, "Thread Started");
      initBall();
      while(isRunning){
        invalidate();
        try {
          //move the ball
          ball_x += x_vel;
          ball_y += y_vel;
          
          /*wall bound checks. When a collision occurs, the corresponding velocity
           * is inverted*/      
          if(ball_y < 0){ //the to edge
            ball_y = 0;
            y_vel = -y_vel;
          }
         
          if(ball_x < 0){ //the left edge 
            ball_x = 0;
            x_vel = -x_vel;
          }
          
          if(ball_x + ball_length > scr_width ){  //the right edge
            ball_x = scr_width - ball_length;
            x_vel = -x_vel;
          }
          
          /*check if ball hit paddle */
          //ball is at the same level as the paddle
          if((ball_y + ball_length >= pad_y) && (ball_y + ball_length < pad_y + 5)){
            //ball is within the length of the paddle
            if((ball_x <= pad_x + pad_length) && (ball_x + ball_length >= pad_x)){
              ball_y = pad_y - (ball_length + 6);
              y_vel = -y_vel;
             
              //if the paddle's velocity is in the opposite direction of the ball's
              //velocity, transfer some of the paddles x-velocity to the balls y-velocity
              if(pad_vel * x_vel >= 0){
                Log.d(TAG, "opposite x-vel vector");
                y_vel -= Math.abs(pad_vel)/2;
                x_vel -= pad_vel/2;
              }
              //paddle and ball are moving in same direction, transfer all pad-vel to ball
              //x-vel
              else{
                Log.d(TAG, "same x-vel vector");
                x_vel -= pad_vel; //alter speed of ball based on speed of paddle
              }
            }
          }
          
          //check if player missed the ball. if they did, restart game
          if(ball_y >= scr_height){
            Thread.sleep( 1000 );
            initBall();
            continue;
          }
          ball.setBounds( ball_x, ball_y , ball_x + ball_length, ball_y + ball_length );
          Thread.sleep( 50 );
        }
        catch ( InterruptedException e ) {
          Log.d(TAG, e.toString());
        }
      }
      Log.d(TAG, "Thread Stopped");
    }    
    /*
     * Called when the game starts and every time the player misses the ball
     */
    private void initBall(){
      //initial position and color of ball
      ball.getPaint().setColor( Color.WHITE );
      ball_x = scr_width/2; ball_y = scr_height/2;
      ball.setBounds( ball_x , ball_y , ball_x + ball_length, ball_y + ball_length );
      
      //initial velocity of ball
      Random num_gen = new Random();
      x_vel = num_gen.nextInt(21) - 10;  //values: [-5, 5]
      y_vel = num_gen.nextInt(21) - 10;  //values: [-5, 5]
    }
  }
}
